/**
 *
 */
package de.cbf.cam.dsa.model.entities;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;

import org.hibernate.annotations.CollectionOfElements;

import de.cbf.cam.dsa.model.ModelBase;
import de.cbf.cam.dsa.model.entities.constants.ActionConstants;
import de.cbf.cam.dsa.model.entities.states.OneState;
import de.cbf.cam.dsa.model.entities.states.StateOnGround;
import de.cbf.cam.dsa.model.entities.states.stateChanger.StateChangerBase;
import de.cbf.cam.dsa.model.entities.weapon.WeaponInUse;
import de.cbf.cam.dsa.model.entities.weapon.WeaponMelee;
import de.cbf.cam.dsa.model.persistence.Header;
import de.cbf.cam.dsa.model.util.Position3f;
import de.cbf.util.modifier.entities.ModifierManager;
import de.cbf.util.modifier.entities.OneModifier;

/**
 * @author Nebelritter
 *
 */
@Entity
public class BattleChar extends Header{

	/**
	 * @uml.property  name="character"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	private OneNPC character;

	/**
	 * @uml.property  name="name"
	 */
	private String name;

	/**
	 * @uml.property  name="side"
	 */
	protected int side;

	/**
	 * @uml.property  name="enemysDistance"
	 * @uml.associationEnd  qualifier="this:de.cbf.cam.dsa.model.entities.BattleChar de.cbf.cam.dsa.model.entities.FightPosition"
	 */
	protected Map<BattleChar,FightPosition> enemysDistance = new HashMap<BattleChar,FightPosition>();

	/**
	 * @uml.property  name="actions"
	 * @uml.associationEnd  multiplicity="(0 -1)" inverse="actor:de.cbf.cam.dsa.model.entities.OneAction"
	 */
	protected List<OneAction> actions = new ArrayList<OneAction>();


	/**
	 * actionsLeft somebody is busy with doing longer actionsLeft(magic etc.)
	 * @uml.property  name="waitingActions"
	 */
	protected int waitingActions;
	/**
	 * @uml.property  name="stateChangersEndRound"
	 * @uml.associationEnd  multiplicity="(0 -1)" elementType="de.cbf.cam.dsa.model.entities.states.stateChanger.StateChangerBase"
	 */
	protected List<StateChangerBase> stateChangersEndRound = new ArrayList<StateChangerBase>();

	/**
	 * @uml.property  name="eBE"
	 */
	protected int eBE;
	/**
	 * @uml.property  name="modifierManager"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	protected ModifierManager 	modifierManager	= new ModifierManager();
	/**
	 * @uml.property  name="stateManager"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	protected StateManager 		stateManager 	= new StateManager();

	/**
	 * @uml.property  name="position"
	 * @uml.associationEnd  multiplicity="(1 1)"
	 */
	protected Position3f position;

	public static String PROPERTYNAME= "BattleChar:";
	/**
	 * @uml.property  name="propertyChangeSupport"
	 */
	public PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
	/**
	 * @uml.property  name="propertyChangeAdvance"
	 * @uml.associationEnd  multiplicity="(1 1)" inverse="this$0:de.cbf.cam.dsa.model.entities.BattleChar$PropertyChangeAdvanceListener"
	 */
	private PropertyChangeAdvanceListener propertyChangeAdvance = new PropertyChangeAdvanceListener();

//========================
//		constructors
//========================

	/**
	 *
	 */
	public BattleChar(OneNPC character,int diceThrow,Position3f position) {
		//take over data
		this.character 	= character;
		this.name 		= character.getName();
		this.guid 		= character.getGuid();
		this.position	= position;
		//get ini
		int ini = getIni(diceThrow);
		calcActions(ini);
		//
		waitingActions = 0;
		//register propertyChangeListener at sub objects
		this.character		.propertyChangeSupport.addPropertyChangeListener(propertyChangeAdvance);
		this.stateManager	.propertyChangeSupport.addPropertyChangeListener(propertyChangeAdvance);
		this.modifierManager.propertyChangeSupport.addPropertyChangeListener(propertyChangeAdvance);
	}



//========================
//	   public methods
//========================

	/**
	 * @return
	 * @uml.property  name="name"
	 */
	public String getName(){
		return name;
	}
	/**
	 * @param newName
	 * @uml.property  name="name"
	 */
	public void setName(String newName) {
		this.name = newName;
	}

	/**
	 * @return the ini
	 */
	@Transient
	public int getIni() {
		List<OneAction> attackActions = getTypedActions(ActionConstants.ATTACK_ACTION);
		int ini = 0;
		for (Iterator<OneAction> iterator = attackActions.iterator(); iterator.hasNext();) {
			OneAction action = (OneAction) iterator.next();
			if(action.getIni()>ini){
				ini=action.getIni();
			}
		}
		return ini;
	}

	public int getIni(int iniRoll) {
		int result=0;
		OneAttribute iniBase = character.getAttribute(OneAttribute.INIBase);
		Integer iniValue	 = iniBase.getCurrentValue();
		//calculate armor Be
		Integer armorBe;
		try{
			armorBe = character.getArmor().getBe();
		}catch (NullPointerException e) {
			armorBe = 0;
		}
		//calculate weaponMain factor on available weapons
		Integer weaponFactor=0;
		WeaponInUse weapon = character.getWeapon(OneNPC.Hand_Main);
		if (weapon instanceof WeaponMelee) {
			WeaponMelee weaponMelee = (WeaponMelee) weapon;
			weaponFactor=weaponMelee.getIniMod();
		}
		//calculate secondary weapon factor
		weapon = character.getWeapon(OneNPC.Hand_Second);
		if (weapon instanceof WeaponMelee) {
			WeaponMelee weaponMelee = (WeaponMelee) weapon;
			weaponFactor+=weaponMelee.getIniMod();
		}
		// sum up ini and return
		result=iniValue-armorBe+weaponFactor+iniRoll;
		//
		return result;
	}

	public int findINIDifference(BattleChar enemy){
		int enemyINI	 = enemy.getIni();
		return getIni()-enemyINI;
	}

	public void calcActions(int ini) {
		List<OneAction> actions = new ArrayList<OneAction>();
		OneAction actionToAdd;
		//add standard actions
		actionToAdd = ModelBase.getInstance().getActionForCharacter(ActionConstants.ATTACK_ACTION, this);
		actionToAdd.setIni(ini);
		actions.add(actionToAdd);
		//
		actionToAdd = ModelBase.getInstance().getActionForCharacter(ActionConstants.DEFEND_ACTION, this);
		actionToAdd.setIni(-1);
		actions.add(actionToAdd);
		//
		actionToAdd = ModelBase.getInstance().getActionForCharacter(ActionConstants.FREE_ACTION, this);
		actionToAdd.setIni(-1);
		actions.add(actionToAdd);
		//
		this.actions = actions;
	}

	/**
	 * method to get a list of actions of character with a specific type
	 * @param typeWanted
	 * @return
	 */
	@Transient
	public List<OneAction> getTypedActions(int typeWanted) {
		List<OneAction> activeActions = new ArrayList<OneAction>();
		for(int i =0; i < this.actions.size();i++){	//for all actions of character
			int type = actions.get(i).type;			//get type of action
			if((type & typeWanted) == typeWanted){ 	//bit wise ANDing leaves only amount of actionType wanted
				activeActions.add(actions.get(i));	//if type correct add to result
			}
		}
		return activeActions;
	}
	/**
	 * method to get actions with desired type and realisation status
	 * @param typeWanted
	 * @param realizationStatus
	 * @return
	 */
	@Transient
	public List<OneAction> getTypedActions(int typeWanted,boolean realizationStatus) {
		List<OneAction> allActions = getTypedActions(typeWanted);	// get all actions of type
		for(int i =0; i<allActions.size();i++){
			if(allActions.get(i).realized != realizationStatus){	//remove actions if realization status is wrong
				allActions.remove(i);
			}
		}
		return allActions;
	}


	public List<OneAction> getActions(boolean realizationStatus) {
		List<OneAction> resultList = new ArrayList<OneAction>();
		List<OneAction> allActions = getActions();
		for(int i =0; i<allActions.size();i++){
			if(allActions.get(i).realized == realizationStatus){
				resultList.add(allActions.get(i));
			}
		}
		return resultList;
	}

	public void removeAllActions() {
		actions.clear();
	}

	public void removeOneActionOfType(int type){
		//TODO implement
	}

	public boolean removeAction(OneAction action ){
		return actions.remove(action);
	}

	/**
	 * method to set realization status of all actions to unrealized
	 */
	public void unrealizeAllActions() {
		for(int i =0;i<actions.size();i++){
			actions.get(i).setRealized(false);
		}
	}
	/**
	 * method to handle so called "Patzer" in close combat
	 * @param blunderValue
	 */
	public void blunder(int blunderValue){
		if(blunderValue==2){
			//lost weaponMain
		}
		if( 3 <= blunderValue&&blunderValue <=  5){
			//fall ground
			StateOnGround onGround = new StateOnGround();
			addState(onGround);
		}
		if( 6 <= blunderValue&&blunderValue <=  8){
			//stumble
		}
		if( 8 <= blunderValue&&blunderValue <=  9){
			//fall ground
			StateOnGround onGround = new StateOnGround();
			addState(onGround);
		}
		if(10 <= blunderValue&&blunderValue <= 11){
			//hurt self light
		}
		if(		 			   blunderValue == 12){
			//hurt self heavy
		}
	}

	public List<StateChangerBase> addAndEffectWound(OneWound wound){
		List<StateChangerBase> stateChangersWound = character.addAndEffectWound(wound);
		for(int i=0;i<stateChangersWound.size();i++){
			stateChangersWound.get(i).changeStateBattle(this);
		}
		propertyChangeSupport.firePropertyChange(PROPERTYNAME+"wound:"+wound.zone, null, this);
		return stateChangersWound;
	}

	public void healWound(String zone){
		List<StateChangerBase> stateChangersHealBattle = character.healWound(zone);
		for(int i=0;i<stateChangersHealBattle.size();i++){
			stateChangersHealBattle.get(i).changeStateBattle(this);
		}
	}

	public void healWound(){
		List<StateChangerBase> stateChangersHealBattle = character.healWound();
		for(int i=0;i<stateChangersHealBattle.size();i++){
			stateChangersHealBattle.get(i).changeStateBattle(this);
		}
	}

	/**
	 * method to compare two character in battle
	 * ( at the moment only using name as only factum)
	 * @param other
	 * @return
	 */
	public boolean isSameAs(BattleChar other){
		if(this.character.getName() == other.character.getName()){
			return true;
		}
		return false;
	}
//========================
//    standard methods
//========================
	@Override
	public String toString() {
		return character.toString();
	}

//========================
// 	   private methods
//========================


//========================
// 	SHORT property access
//========================

	/**
	 * method that adds an enemy to this char and this char to the enemies enemies
	 * @param enemy
	 */
	public void addEnemy(BattleChar enemy,FightPosition fightPosition){
		this.enemysDistance.put(enemy,fightPosition);
		enemy.enemysDistance.put(this,fightPosition);
	}
	/**
	 * method that removes an enemy from this char and this char from the enemies enemies
	 * @param enemy
	 */
	public void removeEnemy(BattleChar enemy){
		this.enemysDistance.remove(enemy);
		enemy.enemysDistance.remove(this);
	}

	public void removeAllEnemys(){
		List<BattleChar> enemys = getEnemysNames();
		for(int i = 0;i<enemys.size();i++){
			removeEnemy(enemys.get(i));
		}
	}

	/**
	 * method to get the waiting actionsLeft as string and "" if they are 0
	 * @return
	 */
	@Transient
	public String getWaitingActionSTR() {
		if(waitingActions==0){
			return "";
		}
		return waitingActions+"";
	}

	/**
	 * method to get the current enemys of this character in battle
	 * DO NOT add or remove here,cause this list is not setting values activly
	 * @return
	 */
	@Transient
	public List<BattleChar> getEnemysNames() {
		List<BattleChar> resultList=new ArrayList<BattleChar>();
		resultList.addAll(enemysDistance.keySet());
		return resultList;
	}

	public Set<Entry<BattleChar,FightPosition>> getEnemysFull(){
		return enemysDistance.entrySet();
	}
	@Transient
	public String getWoundsDescription() {
		//get the wound description String (<number>) <zone>,<zone>,...   get woundManager from character
		return "";
	}

	@Transient
	public String getATActionsString(){
		StringBuffer buf = new StringBuffer(3);
		List<OneAction> attackActions =  getTypedActions(ActionConstants.ATTACK_ACTION);
		List<OneAction> leftAttackActions = new ArrayList<OneAction>();
		for (OneAction oneAction : attackActions) {
			if(!oneAction.realized){
				leftAttackActions.add(oneAction);
			}
		}
		buf.append(leftAttackActions.size());
		buf.append("/");
		buf.append(attackActions.size());
		return buf.toString();
	}
	@Transient
	public String getPAActionsString(){
		StringBuffer buf = new StringBuffer(3);
		List<OneAction> defenseActions = getTypedActions(ActionConstants.DEFEND_ACTION);
		List<OneAction> leftDefenseActions = new ArrayList<OneAction>();
		for (OneAction oneAction : defenseActions) {
			if(!oneAction.realized){
				leftDefenseActions.add(oneAction);
			}
		}
		buf.append(leftDefenseActions.size());
		buf.append("/");
		buf.append(defenseActions.size());
		return buf.toString();
	}
	@Transient
	public String getFreeActionsString(){
		StringBuffer buf = new StringBuffer(3);
		List<OneAction> freeActions =  getTypedActions(ActionConstants.FREE_ACTION);
		List<OneAction> leftFreeActions = new ArrayList<OneAction>();
		for (OneAction oneAction : freeActions) {
			if(!oneAction.realized){
				leftFreeActions.add(oneAction);
			}
		}
		buf.append(leftFreeActions.size());
		buf.append("/");
		buf.append(freeActions.size());
		return buf.toString();
	}

	public void addModifier(OneModifier modifier) {
		this.modifierManager.addModifier( modifier);
	}
	public void removeModifier(OneModifier modifier) {
		this.modifierManager.removeModifier( modifier);
	}
	public void removeAllModifierOfType(Integer type) {
		this.modifierManager.removeAllModifiersOfType(type);
	}
	@Transient
	public Integer getModifierValue() {
		return modifierManager.getModifier();
	}
	public void addState(OneState state) {
		this.stateManager.addState(state);
		state.resolveOnCharacter(this);
	}
	public void removeState(OneState state) {
		this.stateManager.removeState(state);
		state.restoreOnCharacter(this);
	}
	@Transient
	public String getStatesDescription() {
		return this.stateManager.getStatesDescription();
	}
//========================
//		getters
//========================

	/**
	 * @return  the character
	 * @uml.property  name="character"
	 */
	@OneToOne(cascade=CascadeType.ALL)
	public OneNPC getCharacter() {
		return character;
	}
	@Transient
	public WeaponInUse getWeapon(String hand) {
		return character.getWeapon(hand);
	}

	/**
	 * @return
	 * @uml.property  name="waitingActions"
	 */
	public int getWaitingActions() {
		return waitingActions;
	}


	/**
	 * @return
	 * @uml.property  name="side"
	 */
	public int getSide() {
		return side;
	}
	@Transient
	public WeaponInUse getMainWeapon(){
		return character.weaponMain;
	}


	@Transient
	public WeaponInUse getSecondWeapon(){
		return character.weaponSecond;
	}


	@Transient
	public List<OneModifier> getModifiers() {
		return modifierManager.getModifiers();
	}

	/**
	 * @return  the modifierManager
	 * @uml.property  name="modifierManager"
	 */
	@OneToOne(cascade=CascadeType.ALL)
	public ModifierManager getModifierManager() {
		return modifierManager;
	}

	/**
	 * @return  the stateManager
	 * @uml.property  name="stateManager"
	 */
	@OneToOne(cascade=CascadeType.ALL)
	public StateManager getStateManager() {
		return stateManager;
	}
	/**
	 * @return
	 * @uml.property  name="position"
	 */
	@OneToOne(cascade=CascadeType.ALL)
	public Position3f getPosition() {
		return position;
	}
	/**
	 * @return the stateChangersEndRound
	 */
	@ManyToMany(cascade=CascadeType.ALL)
	public List<StateChangerBase> getStateChangerEndRound() {
		return stateChangersEndRound;
	}
//========================
//		setters
//========================

	/**
	 * @param character  the character to set
	 * @uml.property  name="character"
	 */
	public void setCharacter(OneNPC character) {
		this.character = character;
	}


	/**
	 * @param waitingActions
	 * @uml.property  name="waitingActions"
	 */
	public void setWaitingActions(int waitingActions) {
		this.waitingActions = waitingActions;
	}



	/**
	 * @param  side
	 * @uml.property  name="side"
	 */
	public void setSide(int side) {
		this.side = side;
	}

	/**
	 * @param position
	 * @uml.property  name="position"
	 */
	public void setPosition(Position3f position) {
		this.position = position;
		propertyChangeSupport.firePropertyChange(PROPERTYNAME+"position", null, this);
	}

	public boolean addEndRoundState(StateChangerBase stateChanger){
		return stateChangersEndRound.add(stateChanger);
	}
	public boolean removeEndRoundState(StateChangerBase stateChanger){
		return stateChangersEndRound.remove(stateChanger);
	}

	/**
	 * @return the enemysDistance
	 */
	@CollectionOfElements
	public Map<BattleChar, FightPosition> getEnemysDistance() {
		return enemysDistance;
	}

	/**
	 * @param enemysDistance the enemysDistance to set
	 */
	protected void setEnemysDistance(Map<BattleChar, FightPosition> enemysDistance) {
		this.enemysDistance = enemysDistance;
	}

	/**
	 * @return the actions
	 */
	@ManyToMany(cascade=CascadeType.ALL)
	public List<OneAction> getActions() {
		return actions;
	}

	/**
	 * @param actions the actions to set
	 */
	protected void setActions(List<OneAction> actions) {
		this.actions = actions;
	}

	/**
	 * @param stateChangersEndRound the stateChangersEndRound to set
	 */
	protected void setStateChangerEndRound(
			List<StateChangerBase> stateChangerEndRound) {
		this.stateChangersEndRound = stateChangerEndRound;
	}

	/**
	 * @param modifierManager  the modifierManager to set
	 * @uml.property  name="modifierManager"
	 */
	protected void setModifierManager(ModifierManager modifierManager) {
		this.modifierManager = modifierManager;
	}

	/**
	 * @param stateManager  the stateManager to set
	 * @uml.property  name="stateManager"
	 */
	protected void setStateManager(StateManager stateManager) {
		this.stateManager = stateManager;
	}

	@Override
	@Id
	public String getGuid() {
		return guid;
	}

	@Override
	protected void setGuid(String guid) {
		this.guid = guid;
	}
//========================
//	listeners
//========================
	private class PropertyChangeAdvanceListener implements PropertyChangeListener{
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			propertyChangeSupport.firePropertyChange(PROPERTYNAME+evt.getPropertyName(),null,BattleChar.this);
		}
	}


}
